#include <iostream>
#include <iomanip>
#include <string>

#include "sendsignal_f.h"
#include "sigma_function.h"

#include "sendsignal_f_study.h"
#include "sendsignal_b_study.h"

#include "study.h"
#include "study/study_v1.h"
#include "study/bruteforce.h"
#include "study/study_send.h"
#include "study/findNeuronMaxError.h"
#include "study/sendbsignalbp.h"
#include "math_ext.h"


const static int    fieldPrecision = 5;

static void printNeuroNet(const int neuron_count_max, int syn_count_max);
static void printNeuroNetStudy(const int neuron_count_max, int syn_count_max);
using namespace std;

const int init_layer_count_max     = 3;//20; //5
const int max_neuron_count_in_layer = 2;

sendsignal *psignal_f = null;
neuron     *pneuron   = null;

sendsignal_f_study   *pstudy_f_signal = null;
sendsignal_b_study   *pstudy_b_signal = null;
double      f_error;
double f_error_study_f;
double f_error_study_b;

const static double exp_zerro_value = 0.00001;
const static double exp_one_value = 0.9999;
ntype::signal_t in_signal_0[] = {exp_one_value,    exp_zerro_value};
ntype::signal_t in_signal_1[] = {exp_zerro_value,  exp_one_value};
ntype::signal_t in_signal_2[] = {exp_zerro_value,  exp_zerro_value};
ntype::signal_t in_signal_3[] = {exp_one_value,    exp_one_value};

ntype::signal_t out_exp_signal_0[] = {exp_one_value,   exp_zerro_value};
ntype::signal_t out_exp_signal_1[] = {exp_one_value,   exp_zerro_value};
ntype::signal_t out_exp_signal_2[] = {exp_zerro_value, exp_zerro_value};
ntype::signal_t out_exp_signal_3[] = {exp_zerro_value, exp_zerro_value};


//#define SIGNAL_ID  1
#define SIGNAL_VALUE(signalName, id)  signalName##_##id
ntype::signal_t *in_signal      = SIGNAL_VALUE(in_signal, 1);
ntype::signal_t *out_exp_signal = SIGNAL_VALUE(out_exp_signal, 1);
//double *in_signal = in_signal_1;
//double *out_exp_signal = out_exp_signal_1;

const static size_t fieldWidth = 20;



study   *pstudy;

int main()
{
  const int neuron_count_max = init_layer_count_max*max_neuron_count_in_layer;
  int syn_count_max;
  neuron_calc  *p_calc  = new sigma_function();

  pneuron = new neuron [neuron_count_max];
//  pstudy  = new study_v1();
//  pstudy  = new BruteForce();
  pstudy  = new study_send();

  //manualy synapse weight set
  //neuron_count_max = 6
  //max_neuron_count_in_layer = 2
  ntype::weight_t nweight[][max_neuron_count_in_layer] =
  {
//layer 0
      //neuron 0
      {1.0, 1.0},
      //neuron 1
      {1.0, 1.0},
//layer 1
      //neuron 2
      {1.0, 1.0},
      //neuron 3
      {1.0, 1.0},
//layer 2
      //neuron 4
      {1.0, 1.0},
      //neuron 5
      {1.0, 1.0},

//layer 3
      //neuron 6
      {0.285336, 0.29282},
      //neuron 7
      {0.285336, 0.29282},
//layer 4
      //neuron 8
      {0.285336, 0.29282},
      //neuron 9
      {0.285336, 0.29282},
//layer 5
      //neuron 10
      {0.194888, 0.2},
      //neuron 11
      {0.194888, 0.2},
//layer 6
      //neuron 12
      {0.194888, 0.2},
      //neuron 13
      {0.194888, 0.2},
//layer 7
      //neuron 14
      {0.194888, 0.2},
      //neuron 15
      {0.194888, 0.2},
//layer 8
      //neuron 16
      {0.194888, 0.2},
      //neuron 17
      {0.194888, 0.2},
//layer 9
      //neuron 18
      {0.194888, 0.2},
      //neuron 19
      {0.194888, 0.2},
//layer 10
      //neuron 20
      {0.194888, 0.2},
      //neuron 21
      {0.194888, 0.2},
//layer 11
      //neuron 22
      {0.194888, 0.2},
      //neuron 23,
      {0.194888, 0.2},
//layer 12
      //neuron 24
      {0.194888, 0.2},
      //neuron 25,
      {0.194888, 0.2},
//layer 13
      //neuron 26
      {0.194888, 0.2},
      //neuron 27,
      {0.194888, 0.2},
//layer 14
      //neuron 28
      {5.23378, 5.23378},
      //neuron 29,
      {0.194888, 0.2},
//layer 15
      //neuron 30
      {0.194888, 0.2},
      //neuron 31,
      {0.194888, 0.2},
//layer 16
      //neuron 32
      {0.194888, 0.2},
      //neuron 33,
      {0.194888, 0.2},
//layer 17
      //neuron 34
      {0.194888, 0.2},
      //neuron 35,
      {0.194888, 0.2},
//layer 18
      //neuron 36
      {0.194888, 0.2},
      //neuron 37,
      {0.194888, 0.2},
//layer 19
      //neuron 38
      {0.194888, 0.2},
      //neuron 39,
      {0.194888, 0.2}
  };



  cout << setw(fieldWidth) << left << "signal id    |"
       << setw(fieldWidth) << left << "sig in[0]    |"
       << setw(fieldWidth) << left << "sig in[1]    |"
       << setw(fieldWidth) << left << "sig out[0]   |"
       << setw(fieldWidth) << left << "sig out[1]   |"
       << std::endl;

     cout << setw(fieldWidth) << left << 0
          << setw(fieldWidth) << left << in_signal_0[0]
          << setw(fieldWidth) << left << in_signal_0[1]
          << setw(fieldWidth) << left << out_exp_signal_0[0]
          << setw(fieldWidth) << left << out_exp_signal_0[1]
          << std::endl;

     cout << setw(fieldWidth) << left << 1
          << setw(fieldWidth) << left << in_signal_1[0]
          << setw(fieldWidth) << left << in_signal_1[1]
          << setw(fieldWidth) << left << out_exp_signal_1[0]
          << setw(fieldWidth) << left << out_exp_signal_1[1]
          << std::endl;

     cout << setw(fieldWidth) << left << 2
          << setw(fieldWidth) << left << in_signal_2[0]
          << setw(fieldWidth) << left << in_signal_2[1]
          << setw(fieldWidth) << left << out_exp_signal_2[0]
          << setw(fieldWidth) << left << out_exp_signal_2[1]
          << std::endl;

     cout << setw(fieldWidth) << left << 3
          << setw(fieldWidth) << left << in_signal_3[0]
          << setw(fieldWidth) << left << in_signal_3[1]
          << setw(fieldWidth) << left << out_exp_signal_3[0]
          << setw(fieldWidth) << left << out_exp_signal_3[1]
          << std::endl;

/** Init section **/
  for(int neuron_count = 0; neuron_count < neuron_count_max; neuron_count ++)
  {
  	pneuron[neuron_count].setNeuronID(neuron_count);
  	pneuron[neuron_count].setCalcFunction(p_calc);
    pneuron[neuron_count].setSynapseCount(max_neuron_count_in_layer);
    syn_count_max = pneuron[neuron_count].getSynapseCount();
    for(int synapse_count = 0; synapse_count < syn_count_max; synapse_count ++)
    {
//        pneuron[neuron_count].getSynapse(synapse_count).setWeight(1.);
        pneuron[neuron_count].getSynapse(synapse_count).setWeight(nweight[neuron_count][synapse_count]);

/*
        static double output = 0.0;
        pneuron[neuron_count].getSynapse(synapse_count).setWeight(output);
        cout << "n:" << neuron_count<< " max id: "<<  neuron_count_max << " s:" << synapse_count << "w:" << pneuron[neuron_count].getSynapse(synapse_count).getWeight() << " output: "<< output<< std::endl;
        output = (double)neuron_count / (double)neuron_count_max;
*/
    }
//    pneuron[neuron_count].calc_study();
  }

#if 0
/* start: manualy study */
  sendsignal_f f_signal_1;
  f_signal_1.setneuronet(pneuron, neuron_count_max);
  f_signal_1.setlayersize(max_neuron_count_in_layer);

  f_signal_1.send(in_signal_0, max_neuron_count_in_layer);

  std::cout << "in sig: " << in_signal_0[0] << " : " << in_signal_0[1] << std::endl;
  std::cout << "exp sig: " << out_exp_signal_0[0] << " : " << out_exp_signal_0[1] << std::endl;


  printNeuroNet(neuron_count_max, max_neuron_count_in_layer);

  return 0;
/*end: manualy study */

#endif

#if 0
/** send forward signal section */
  sendsignal_f f_signal;
  f_signal.setneuronet(pneuron, neuron_count_max);
  f_signal.setlayersize(max_neuron_count_in_layer);


  cout << "***** send real forward signal *****" << endl;
  f_signal.send(in_signal, max_neuron_count_in_layer);

  f_error = f_signal.geterror(out_exp_signal);

  cout << "forward signal error: " << f_error << endl;

/** print in-signal */
    cout << "***** input signal *****" << endl;
    cout << " id              value" << endl;
    for(int in_count = 0; in_count < max_neuron_count_in_layer; in_count ++)
    {
       cout << in_count <<"            "<<  in_signal[in_count] << endl;
    }

/** print out-signal */
    cout << "***** output signal *****" << endl;
    cout << " id      value" << endl;
    int start_neuron = f_signal.getstartneuronidforlayer(init_layer_count_max-1);
    for(int out_count = start_neuron; out_count < neuron_count_max; out_count ++)
    {
       neuron     *p_local_neuron = f_signal.getneuron(out_count);
       axon        local_axon = p_local_neuron->getAxon();
       cout << out_count <<"            "<< p_local_neuron->getAxon().getFSignal() << endl;
    }

int signal_study_id = 0;
/** send forward signal study **/

pstudy_f_signal = new sendsignal_f_study();
pstudy_f_signal->setneuronet(pneuron, neuron_count_max);
pstudy_f_signal->setlayersize(max_neuron_count_in_layer);

cout << "***** send forward signal [study mode] *****" << endl;
pstudy_f_signal->setstudysignalid(signal_study_id);
pstudy_f_signal->setinsignal(in_signal, max_neuron_count_in_layer);

 cout << " id            value" << endl;
 for(int in_count = 0; in_count < max_neuron_count_in_layer; in_count ++)
 {
    cout << in_count <<"            "<<  in_signal[in_count] << endl;
 }
pstudy_f_signal->send(null, 0);

f_error_study_f = pstudy_f_signal->geterror(in_signal);

/** print out-signal [study mode]*/
    cout << "***** output signal [study mode] *****" << endl;
    cout << " id      value" << endl;
    start_neuron = pstudy_f_signal->getstartneuronidforlayer(2);
    for(int out_count = start_neuron; out_count < neuron_count_max; out_count ++)
    {
       neuron     *p_local_neuron = pstudy_f_signal->getneuron(out_count);
       axon        local_axon = p_local_neuron->getAxon();
       cout << out_count <<"            "<< p_local_neuron->getAxon().getFSignal() << endl;
    }
    cout << std::endl;

/** expected signal **/
    cout << "***** expected out signal *****" << endl;
    cout << setw(fieldWidth) << left << "id"
         << setw(fieldWidth) << left << "value"
         << std::endl;
    for(int exp_count = 0; exp_count < max_neuron_count_in_layer; exp_count ++)
    {
        cout << setw(fieldWidth) << left << exp_count
             << setw(fieldWidth) << left << out_exp_signal[exp_count]
             << std::endl;
    }
    cout << std::endl;


/** send backward signal study **/

pstudy_b_signal = new sendsignal_b_study();
pstudy_b_signal->setneuronet(pneuron, neuron_count_max);
pstudy_b_signal->setlayersize(max_neuron_count_in_layer);

pstudy_b_signal->setstudysignalid(0);
pstudy_b_signal->setoutputsignal(out_exp_signal, max_neuron_count_in_layer);
//pstudy_b_signal->setoutputsignalfromaxon(0);

pstudy_b_signal->send(null, 0);

f_error_study_f = pstudy_b_signal->geterror();
#endif
///** print back-in-signal [study mode]*/
//    cout << "***** back-in-signal signal [study mode] *****" << endl;
//    cout << " id      value" << endl;
//    neuron     *p_local_neuron = pstudy_b_signal->getneuron(0);
//    for(int out_count = 0; out_count < max_neuron_count_in_layer; out_count ++)
//    {
//       cout << out_count <<"            "<< p_local_neuron->getSynapse(out_count).getBSignalStudy(signal_study_id) << endl;
//    }

#if 0
//set weight
for(int neuron_count = 0; neuron_count < neuron_count_max; neuron_count ++)
{
  syn_count_max = pneuron[neuron_count].getSynapseCount();
  for(int synapse_count = 0; synapse_count < syn_count_max; synapse_count ++)
  {
      pneuron[neuron_count].getSynapse(synapse_count).setWeight(nweight[neuron_count][synapse_count]);
  }
}
#endif
//print all neuro net
//   printNeuroNet(neuron_count_max, syn_count_max);
#if 0
    cout << setw(fieldWidth) << left << "neuron id |"
         << setw(fieldWidth) << left << "syn id    |"
         << setw(fieldWidth) << left << "w         |"
         << setw(fieldWidth) << left << "syn.fwd   |"
         << setw(fieldWidth) << left << "axon.fwd  |"
         << setw(fieldWidth) << left << "syn.back  |"
         << setw(fieldWidth) << left << "axon back |"
         << std::endl;
    for(int neuron_count = 0; neuron_count < neuron_count_max; neuron_count ++)
    {
      syn_count_max = pneuron[neuron_count].getSynapseCount();
      for(int synapse_count = 0; synapse_count < syn_count_max; synapse_count ++)
      {
  //        pneuron[neuron_count].getSynapse(synapse_count).setWeight(1.);
          pneuron[neuron_count].getSynapse(synapse_count).setWeight(nweight[neuron_count][synapse_count]);
          cout << setw(fieldWidth) << left << neuron_count
               << setw(fieldWidth) << left << synapse_count
               << setw(fieldWidth) << left << pneuron[neuron_count].getSynapse(synapse_count).getWeight()
               << setw(fieldWidth) << left << pneuron[neuron_count].getSynapse(synapse_count).getFSignalStudy()
               << setw(fieldWidth) << left << pneuron[neuron_count].getAxon().getFStudySignal()
               << setw(fieldWidth) << left << pneuron[neuron_count].getSynapse(synapse_count).getBSignalStudy()
               << setw(fieldWidth) << left << pneuron[neuron_count].getAxon().getBStudySignal()
               << std::endl;
      }
      cout << std::endl;
    }
#endif
//temprorary switch off study [manual mode]
//        return 0;

sendsignal_f f_signal;
f_signal.setneuronet(pneuron, neuron_count_max);
f_signal.setlayersize(max_neuron_count_in_layer);
f_signal.setminvalue(exp_zerro_value);

pstudy->setneuronet(pneuron);
pstudy->setneuroncount(neuron_count_max);
pstudy->setmaxerror(0.01);

//pstudy_b_signal = new sendsignal_b_study();
pstudy_b_signal = new sendbsignalbp();

pstudy_b_signal->setneuronet(pneuron, neuron_count_max);
pstudy_b_signal->setlayersize(max_neuron_count_in_layer);

pstudy_f_signal = new sendsignal_f_study();
pstudy_f_signal->setneuronet(pneuron, neuron_count_max);
pstudy_f_signal->setlayersize(max_neuron_count_in_layer);

pstudy->setsendsignalbstudy(pstudy_b_signal);
pstudy->setsendsignalfstudy(pstudy_f_signal);


pstudy->addinstudysignal(in_signal_0);
pstudy->addexpextedsignal(out_exp_signal_0);


pstudy->addinstudysignal(in_signal_1);
pstudy->addexpextedsignal(out_exp_signal_1);


pstudy->addinstudysignal(in_signal_2);
pstudy->addexpextedsignal(out_exp_signal_2);

pstudy->addinstudysignal(in_signal_3);
pstudy->addexpextedsignal(out_exp_signal_3);

pstudy->setstudyciclecount(100000000);
//pstudy->setstudyciclecount(1);
pstudy->setsignacountmax(4);



cout << "***** start study class *****" << endl;
pstudy->start();
pstudy->printNeuroNet(0);
pstudy->printNeuroNet(1);
pstudy->printNeuroNet(2);
pstudy->printNeuroNet(3);

cout << "***** send real forward signal commented *****" << endl;



FindNeuronMaxError findNeuronMaxError;
findNeuronMaxError.setneuronet(pneuron, neuron_count_max);
findNeuronMaxError.start();

ntype::neuronid_t max_error_neuron_id = findNeuronMaxError.getNeuronID();
ntype::error_t    max_error_value = findNeuronMaxError.getError();

cout << "--- NEURON_MAX_ERROR ---" << std::endl;
cout << setw(fieldWidth) << left << "neuron id  |"
     << setw(fieldWidth) << left << "err.value  |"
     << setw(fieldWidth) << left << "nn.error   |"
     << std::endl;

cout << setw(fieldWidth) << left << max_error_neuron_id
     << setw(fieldWidth) << left << max_error_value
     << setw(fieldWidth) << left << pstudy->getNeuroNetError()
     << std::endl;


ntype::weight_t w = 0.0;
ntype::synapsecount_t w_syn_count_max = pneuron[max_error_neuron_id].getSynapseCount();
ntype::synapsecount_t syn_count = 0;
cout << "--- AVERAGE BACK SYNAPSE WEIGHT ---" << std::endl;
for(; syn_count < w_syn_count_max; syn_count++)
{
    w = 0.0;
    const ntype::signalid_t max_sig_id = pneuron[max_error_neuron_id].getMaxSignalStudyBufferIndex();
    for(ntype::signalid_t sig_id = 0; sig_id < max_sig_id; sig_id ++)
    {
       pneuron[max_error_neuron_id].setStudyBufferIndex(sig_id);
       w +=pneuron[max_error_neuron_id].clac_back_synapse_w(syn_count);
    }
    w = w / max_sig_id;
    cout << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << syn_count
         << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << w
         << std::endl;
}



return 0;

cout << "***** send real forward signal [after study] *****" << endl;

cout << "***** send signal 0 *****" << endl;
std::cout << "in sig: " << in_signal_0[0] << " : " << in_signal_0[1] << std::endl;
std::cout << "exp sig: " << out_exp_signal_0[0] << " : " << out_exp_signal_0[1] << std::endl;
f_signal.send(in_signal_0, max_neuron_count_in_layer);
f_error = f_signal.geterror(out_exp_signal_0);

cout << "forward signal error [real mode]: " << f_error << " max allowed error: "<< pstudy->getmaxerror() << endl;
printNeuroNet(neuron_count_max, syn_count_max);

cout << endl;
cout << "***** send signal 1 *****" << endl;
std::cout << "in sig: " << in_signal_1[0] << " : " << in_signal_1[1] << std::endl;
std::cout << "exp sig: " << out_exp_signal_1[0] << " : " << out_exp_signal_1[1] << std::endl;
f_signal.send(in_signal_1, max_neuron_count_in_layer);
f_error = f_signal.geterror(out_exp_signal_1);

cout << "forward signal error [real mode]: " << f_error << " max allowed error: "<< pstudy->getmaxerror() << endl;
printNeuroNet(neuron_count_max, syn_count_max);

cout << endl;
cout << "***** send signal 2 *****" << endl;
std::cout << "in sig: " << in_signal_2[0] << " : " << in_signal_2[1] << std::endl;
std::cout << "exp sig: " << out_exp_signal_2[0] << " : " << out_exp_signal_2[1] << std::endl;
f_signal.send(in_signal_2, max_neuron_count_in_layer);
f_error = f_signal.geterror(out_exp_signal_2);

cout << "forward signal error [real mode]: " << f_error << " max allowed error: "<< pstudy->getmaxerror() << endl;
printNeuroNet(neuron_count_max, syn_count_max);

cout << endl;
cout << "***** send signal 3 *****" << endl;
std::cout << "in sig: " << in_signal_3[0] << " : " << in_signal_3[1] << std::endl;
std::cout << "exp sig: " << out_exp_signal_3[0] << " : " << out_exp_signal_3[1] << std::endl;
f_signal.send(in_signal_3, max_neuron_count_in_layer);
f_error = f_signal.geterror(out_exp_signal_3);

cout << "forward signal error [real mode]: " << f_error << " max allowed error: "<< pstudy->getmaxerror() << endl;
printNeuroNet(neuron_count_max, syn_count_max);
  return 0;
}

///the function does not work correctly. the function does not use signal id
void printNeuroNetStudy(const int neuron_count_max, int syn_count_max)
{
    cout << "--- study mode [work incorrect {signal id is not used}]---" << std::endl;
    cout << setw(fieldWidth) << left << "neuron id |"
         << setw(fieldWidth) << left << "syn id    |"
         << setw(fieldWidth) << left << "w         |"
         << setw(fieldWidth) << left << "w.back    |"
         << setw(fieldWidth) << left << "syn.fwd   |"
         << setw(fieldWidth) << left << "axon.fwd  |"
         << setw(fieldWidth) << left << "syn.back  |"
         << setw(fieldWidth) << left << "axon back |"
         << setw(fieldWidth) << left << "axon.error |"
         << std::endl;
    for(int neuron_count = 0; neuron_count < neuron_count_max; neuron_count ++)
    {
      syn_count_max = pneuron[neuron_count].getSynapseCount();
      for(int synapse_count = 0; synapse_count < syn_count_max; synapse_count ++)
      {
          cout << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << neuron_count
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << synapse_count
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].getSynapse(synapse_count).getWeight()
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].clac_back_synapse_w(synapse_count)
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].getSynapse(synapse_count).getFSignalStudy()
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].getAxon().getFStudySignal()
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].getSynapse(synapse_count).getBSignalStudy()
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].getAxon().getBStudySignal()
               << setw(fieldWidth) << std::fixed << left << std::setprecision(fieldPrecision) << pneuron[neuron_count].getAxonStudyError()
               << std::endl;
      }
      cout << std::endl;
    }
    cout << "--------------------------------" << std::endl;
}

void printNeuroNet(const int neuron_count_max, int syn_count_max)
{
    cout << "--- real mode ---" << std::endl;
    cout << setw(fieldWidth) << left << "neuron id |"
         << setw(fieldWidth) << left << "syn id    |"
         << setw(fieldWidth) << left << "w         |"
         << setw(fieldWidth) << left << "syn.fwd   |"
         << setw(fieldWidth) << left << "axon.fwd  |"
         << setw(fieldWidth) << left << "syn.back  |"
         << setw(fieldWidth) << left << "axon back |"
         << std::endl;
    for(int neuron_count = 0; neuron_count < neuron_count_max; neuron_count ++)
    {
      syn_count_max = pneuron[neuron_count].getSynapseCount();
      for(int synapse_count = 0; synapse_count < syn_count_max; synapse_count ++)
      {
          cout << setw(fieldWidth) << left << neuron_count
               << setw(fieldWidth) << left << synapse_count
               << setw(fieldWidth) << left << pneuron[neuron_count].getSynapse(synapse_count).getWeight()
               << setw(fieldWidth) << left << pneuron[neuron_count].getSynapse(synapse_count).getFSignal()
               << setw(fieldWidth) << left << pneuron[neuron_count].getAxon().getFSignal()
               << setw(fieldWidth) << left << pneuron[neuron_count].getSynapse(synapse_count).getBSignal()
               << setw(fieldWidth) << left << pneuron[neuron_count].getAxon().getBSignal()
               << std::endl;
      }
      cout << std::endl;
    }
    cout << "--------------------------------" << std::endl;
}
